﻿namespace PC1
{
    /// <summary>
    /// RISC. ARM.
    /// </summary>
    public class Core
    {
        /// <summary>
        /// ALU - Arithmetic logic unit.
        /// Performs mathematical and logical operations.
        /// </summary>
        private readonly IAlu _alu;

        private readonly IRam _ram;

        /// <summary>
        /// CU - Control unit.
        /// Decodes the program instruction in the IR, selecting machine
        /// resources such as a data source register and a particular
        /// arithmetic operation, and coordinates activation of those
        /// resources.
        /// </summary>
        private object _cu;

        /// <summary>
        /// IR -  Instruction register.
        /// A temporary holding ground for the instruction that has just been
        /// fetched from memory.
        /// </summary>
        private int _ir;

        /// <summary>
        /// MAR - Memory address register.
        /// Holds the address of a memory block to be read from or written to.
        /// </summary>
        private int _mar;

        /// <summary>
        /// MDR - Memory data register.
        /// A two-way register that holds data fetched from memory (and ready
        /// for the CPU to process) or data waiting to be stored in memory.
        /// (This is also known as the memory buffer register (MBR).)
        /// </summary>
        private int _mdr;

        private int _r0;

        private int _r1;

        private int _r2;

        private int _r3;

        private int _r4;

        private int _r5;

        private int _r6;

        private int _r7;

        /// <summary>
        /// PC - Program counter.
        /// An incrementing counter that keeps track of the memory address of
        /// the instruction that is to be executed next or in other words,
        /// holds the address of the instruction to be executed next.
        /// </summary>
        private int _rf;

        public Core(IRam ram)
        {
            _ram = ram;

            _alu = new Alu();

            Reset();
        }

        public int R0
        {
            get { return _r0; }
        }

        public int R1
        {
            get { return _r1; }
        }

        /// <summary>
        /// Program counter.
        /// </summary>
        public int RF
        {
            get { return _rf; }
        }

        private void ADD_R0_R1()
        {
            _alu.SetRIn0(_r0);
            _alu.SetRIn1(_r1);
            _alu.Add();
            _r0 = _alu.GetROut0();
        }

        private void AND_R0_R1()
        {
            _r0 = _r0 & _r1;
        }

        private void BEQ()
        {
            if (_r0 == 0)
            {
                MOV_RF();
            }
            else
            {
                PcInc();
            }
        }

        private void BGQ()
        {
            if (_r0 >= 0)
            {
                MOV_RF();
            }
            else
            {
                PcInc();
            }
        }

        private void BGT()
        {
            if (_r0 > 0)
            {
                MOV_RF();
            }
            else
            {
                PcInc();
            }
        }

        private void BLQ()
        {
            if (_r0 <= 0)
            {
                MOV_RF();
            }
            else
            {
                PcInc();
            }
        }

        private void BLS()
        {
            if (_r0 < 0)
            {
                MOV_RF();
            }
            else
            {
                PcInc();
            }
        }

        private void BNQ()
        {
            if (_r0 != 0)
            {
                MOV_RF();
            }
            else
            {
                PcInc();
            }
        }

        private void CMP_R0_R1()
        {
            _r0 = _r0.CompareTo(_r1);
        }

        private void EOR_R0_R1()
        {
            _r0 = _r0 ^ _r1;
        }

        private int GetFromRamAndPcInc()
        {
            var value = _ram[_rf];
            PcInc();
            return value;
        }

        private void LDR_R0()
        {
            var address = GetFromRamAndPcInc();
            _r0 = _ram[address];
        }

        private void LDR_R1()
        {
            var address = GetFromRamAndPcInc();
            _r1 = _ram[address];
        }

        private void LDR_RF()
        {
            var address = GetFromRamAndPcInc();
            _rf = _ram[address];
        }

        private void MOV_R0()
        {
            _r0 = GetFromRamAndPcInc();
        }

        private void MOV_R0_R1()
        {
            _r0 = _r1;
        }

        private void MOV_R0_RF()
        {
            _r0 = _rf;
        }

        private void MOV_R1()
        {
            _r1 = GetFromRamAndPcInc();
        }

        private void MOV_R1_R0()
        {
            _r1 = _r0;
        }

        private void MOV_R1_RF()
        {
            _r1 = _rf;
        }

        private void MOV_RF()
        {
            _rf = GetFromRamAndPcInc();
        }

        private void MOV_RF_R0()
        {
            _rf = _r0;
        }

        private void MOV_RF_R1()
        {
            _rf = _r1;
        }

        private void ORR_R0_R1()
        {
            _r0 = _r0 | _r1;
        }

        private void PcInc()
        {
            _rf++; // TODO _pc overflow?
        }

        private void STR_R0()
        {
            var address = GetFromRamAndPcInc();
            _ram[address] = _r0;
        }

        private void STR_R1()
        {
            var address = GetFromRamAndPcInc();
            _ram[address] = _r1;
        }

        private void STR_RF()
        {
            var address = GetFromRamAndPcInc();
            _ram[address] = _rf;
        }

        private void SUB_R0_R1()
        {
            _r0 = _r0 - _r1;
        }

        private void Decode(CommandMc command)
        {
            switch (command)
            {
                case CommandMc.ADD_R0_R1:
                    ADD_R0_R1();
                    return;
                case CommandMc.SUB_R0_R1:
                    SUB_R0_R1();
                    return;

                case CommandMc.AND_R0_R1:
                    AND_R0_R1();
                    return;
                case CommandMc.ORR_R0_R1:
                    ORR_R0_R1();
                    return;
                case CommandMc.EOR_R0_R1:
                    EOR_R0_R1();
                    return;

                case CommandMc.CMP_R0_R1:
                    CMP_R0_R1();
                    return;

                case CommandMc.BEQ:
                    BEQ();
                    return;
                case CommandMc.BGQ:
                    BGQ();
                    return;
                case CommandMc.BGT:
                    BGT();
                    return;
                case CommandMc.BLQ:
                    BLQ();
                    return;
                case CommandMc.BLS:
                    BLS();
                    return;
                case CommandMc.BNQ:
                    BNQ();
                    return;

                case CommandMc.LDR_R0:
                    LDR_R0();
                    return;
                case CommandMc.LDR_R1:
                    LDR_R1();
                    return;
                case CommandMc.LDR_RF:
                    LDR_RF();
                    return;
                case CommandMc.STR_R0:
                    STR_R0();
                    return;
                case CommandMc.STR_R1:
                    STR_R1();
                    return;
                case CommandMc.STR_RF:
                    STR_RF();
                    return;

                case CommandMc.MOV_R0:
                    MOV_R0();
                    return;
                case CommandMc.MOV_R1:
                    MOV_R1();
                    return;
                case CommandMc.MOV_RF:
                    MOV_RF();
                    return;

                case CommandMc.MOV_R0_R1:
                    MOV_R0_R1();
                    return;
                case CommandMc.MOV_R0_RF:
                    MOV_R0_RF();
                    return;
                case CommandMc.MOV_R1_R0:
                    MOV_R1_R0();
                    return;
                case CommandMc.MOV_R1_RF:
                    MOV_R1_RF();
                    return;
                case CommandMc.MOV_RF_R0:
                    MOV_RF_R0();
                    return;
                case CommandMc.MOV_RF_R1:
                    MOV_RF_R1();
                    return;
            }
        }

        public void Reset()
        {
            _r0 = 0;
            _r1 = 0;
            _rf = 0;
        }

        public void Step()
        {
            var command = (CommandMc)GetFromRamAndPcInc();
            Decode(command);
        }

        public void Run()
        {
            while (true)
            {
                var command = (CommandMc)GetFromRamAndPcInc();
                if (command == CommandMc.HLT)
                {
                    break;
                }
                Decode(command);
            }
        }
    }
}